﻿using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;

namespace WpfAdvControls
{
    /// <summary>
    ///     Provide command classes for binding to view
    /// </summary>
    public class LibCommand : INotifyPropertyChanged
    {
        private readonly Action action;
        private readonly Action actionCompleted;
        private readonly RoutedCommand command;
        private readonly CommandBinding commandBinding;
        private readonly bool isAsync;

        /// <summary>
        ///     Для вызова обновления визуальных компонент после выполнения команды и после таймаута
        /// </summary>
        private readonly DispatcherTimer dispatcherTimer = new DispatcherTimer
        {
            Interval = TimeSpan.FromMilliseconds(250),
            IsEnabled = false
        };

        private bool isExecuted;
        private Thread thread;

        public LibCommand(Action action)
        {
            this.action = action;
            command = new RoutedCommand();
            commandBinding = new CommandBinding(command, LibCommandExecuted, LibCommandCanExecute);
            dispatcherTimer.Tick += AfterActionTimeoutTick;
        }


        public LibCommand(Action action, bool isAsync)
            : this(action)
        {
            this.isAsync = isAsync;
        }

        public LibCommand(Action action, bool isAsync, Action actionCompleted)
            : this(action, isAsync)
        {
            this.actionCompleted = actionCompleted;
        }

        public bool IsExecuted
        {
            get { return isExecuted; }
            set
            {
                var isChanged = (isExecuted != value);
                //Trace.WriteLine(String.Format("{0} : old:{1}, new:{2}", DateTime.Now, isExecuted, value));
                if (isChanged) isExecuted = value;
                if (isChanged)
                {
                    NotifyPropertyChange("IsExecuted");
                    // start timer to refresh dependent visual elements
                    dispatcherTimer.Start();
                }
            }
        }

        /// <summary>
        ///     Routed command for this Lib Command
        /// </summary>
        public RoutedCommand Command
        {
            get { return command; }
        }

        /// <summary>
        ///     Command binding for the command
        /// </summary>
        public CommandBinding CommandBinding
        {
            get { return commandBinding; }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void AfterActionTimeoutTick(object sender, EventArgs e)
        {
            //Trace.WriteLine(String.Format("{0}: Enabled: {1}", DateTime.Now, dispatcherTimer.IsEnabled));
            // Requery this command
            CommandManager.InvalidateRequerySuggested();
            // Stop timer after one occurence
            if (dispatcherTimer != null)
            {
                dispatcherTimer.Stop();
            }
        }

        private void AsyncAction()
        {
            try
            {
                action();
            }
            finally
            {
                AfterExecuting();
            }
        }

        private void LibCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            IsExecuted = true;
            try
            {
                if (isAsync)
                {
                    thread = new Thread(AsyncAction);
                    thread.Start();
                }
                else
                {
                    action();
                }
            }
            finally
            {
                if (!isAsync)
                {
                    AfterExecuting();
                }
            }
        }

        private void AfterExecuting()
        {
            IsExecuted = false;
            if (actionCompleted != null)
            {
                actionCompleted();
            }
        }

        private void LibCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = !IsExecuted;
            //Trace.WriteLine(String.Format("Time:{0}, Hash:{1}, CanExecute{2}", DateTime.Now, this.GetHashCode(), e.CanExecute));
        }

        /// <summary>
        ///     Уведомляет слушателей об изменении свойства
        /// </summary>
        /// <param name="prop"></param>
        private void NotifyPropertyChange(string prop)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(prop));
            }
        }


        public void ApplyToButton(Button button)
        {
            button.Command = Command;
            button.CommandBindings.Add(CommandBinding);
            CommandManager.AddExecutedHandler(button, LibCommandExecuted);
            CommandManager.AddCanExecuteHandler(button, LibCommandCanExecute);
        }

        /// <summary>
        ///     Abort executing if it is async, of course
        /// </summary>
        public void Abort()
        {
            if (thread.IsAlive && isAsync)
            {
                try
                {
                    thread.Abort();
                }
                finally
                {
                    AfterExecuting();
                }
            }
        }
    }
}